package utils;

import dtos.UserDTO;
import model.Domain;
import model.User;
import model.User_;
import model.Usertype;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;

/**
 * Session Bean implementation class UserDAO
 */

@Stateless
public class UserDAO implements UserDAORemote {

    @PersistenceContext(unitName = "rmtPersistenceUnit")
    private EntityManager em;

    @EJB
    private Conv conv;

    public UserDAO() {
    }

    /**
     * Inserts user in database.
     */
    @Override
    public void insertUser(UserDTO ud) {
        User r = em.find(User.class, ud.getId());
        User r1 = conv.fromEntityUserDTO(ud);
        if (r == null) {
            em.persist(r1);
        } else {
            em.merge(r1);
        }
    }

    /**
     * Method that searches for a User given the user ID
     *
     * @param id an integer value
     * @return a user with the corresponding id
     */
    @Override
    public UserDTO getUser(int id) {
        try {
            return conv.fromEntityUser(em.createNamedQuery("User.findById", User.class).setParameter("id", id).getSingleResult());
        } catch (NoResultException e) {
            return null;
        }

    }

    /*@Override
    public UserDTO getUser(int id) {
        if (em.find(User.class, id) == null)
            return null;
        TypedQuery<User> tq = em.createNamedQuery("User.findById", User.class);
        tq.setParameter("id", id);
        return conv.fromEntityUser(tq.getSingleResult());
    }*/
    /**
     * General update method for User entity.
     *
     * @param ud UserDTO to be updated
     */
    @Override
    public void updateUser(UserDTO ud) {
        updateUser(conv.fromEntityUserDTO(ud));
    }

    private void updateUser(User u) {
        if (em.find(User.class, u.getId()) != null) {
            em.merge(u);
        }
    }

    /**
     * Returns a list of UserDTOs with matching username
     *
     * @param username the username of the user we are searching for
     * @return a list o users
     */
    @Override
    public List<UserDTO> getAllUsersByUsername(String username) {
        List<UserDTO> resultUserList = new ArrayList<>();
        List<User> users = getAllUsers();
        for (User u : users) {
            if (u.getUsername().equals(username)) {
                resultUserList.add(conv.fromEntityUser(u));
            }
        }
        return resultUserList;
    }

    /**
     * Method used to update the password of a certain user
     *
     * @param user     a UserDTO object used for identifying the user whom password
     *                 we have to change
     * @param password the new password of the account
     */
    @Override
    public String updateUserPassword(UserDTO user, String password) {
        String md5Pass = new Codification().md5(user.getPassword());
        String error = checkPassword(getAllUsers(), user.getUsername(), md5Pass);
        System.out.println("eroarea este " + error);
        if (error.equals("")) {
            User u = em.find(User.class, user.getId());
            u.setPassword(md5Pass);
            updateUser(u);
        }

        return error;
    }

    /**
     * Method who makes approximately the same thing as above but doesn't verify user password
     *
     * @param user     the user DTO representing the entity to update
     * @param password the new password (not codified)
     */
    @Override
    public void updateUserPasswordWithCodification(UserDTO user, String password) {
        User u = em.find(User.class, user.getId());
        u.setPassword(new Codification().md5(password));
        updateUser(u);
    }


    /**
     * Method used to modify different data(email/name) of a certain user
     *
     * @param user a UserDTO object used for identifying the user whom data we
     *             have to change and the changed data
     * @return a string with the errors, if any
     */
    @Override
    public String updateUserProfile(UserDTO user) {
        // public String updateUser(UserDTO user, String verifPass) {
        String error = "";

        List<User> users = getAllUsers();

        error = error + checkEmailDuplicate(users, user);
        // error = error + checkPassword(users, user);

        if (error.equals("")) {
            User u = em.find(User.class, user.getId());
            u.setName(user.getName());
            u.setEmail(user.getEmail());
            updateUser(u);
        }
        return error;
    }

    /**
     * Method for getting the user list
     *
     * @return list of users
     */
    private List<User> getAllUsers() {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<User> cq = cb.createQuery(User.class);
        Root<User> user = cq.from(User.class);
        cq.select(user);
        TypedQuery<User> q = em.createQuery(cq);

        return q.getResultList();
    }

    /**
     * Method for checking if the entered password is the same as the one from
     * the database
     *
     * @param users list of all the users
     * @param users a UserDTO object containing a User from the list with modified
     *              password
     * @return error string
     */
    private String checkPassword(List<User> users, String username, String pass) {
        for (User u : users) {
            if (u.getUsername().equals(username)) {
                if (!u.getPassword().equals(pass)) {
                    return "Wrong password";
                }
            }
        }
        return "";

    }

    /**
     * Checking the database for email addresses duplicates
     *
     * @param users list of all the users
     * @param user  a UserDTO object containing a User from the list with modified
     *              email
     * @return error string
     */
    private String checkEmailDuplicate(List<User> users, UserDTO user) {
        int count = 0;
        for (User u : users) {
            if (u.getEmail().equals(user.getEmail())) {
                if (u.getId() != user.getId()) {
                    count++;
                }

            }
        }

        if (count > 0) {
            return "Email duplicate detected";
        }
        return "";
    }

    /**
     * Method for getting the user list
     *
     * @return list of users
     */
    @Override
    public List<UserDTO> getAllUsersDTO() {
        List<UserDTO> resultlist = new ArrayList<>();
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<User> cq = cb.createQuery(User.class);
        Root<User> user = cq.from(User.class);
        cq.select(user);
        TypedQuery<User> q = em.createQuery(cq);

        List<User> users = q.getResultList();

        for (User u : users) {
            resultlist.add(conv.fromEntityUser(u));
        }
        return resultlist;
    }

    /**
     * Method used to delete user
     *
     * @param ud of type user
     */
    @Override
    public void deleteUser(UserDTO ud) {
        User r = em.find(User.class, ud.getId());
        em.remove(r);
    }

    /**
     * Method used to update username
     *
     * @param user     of type username
     * @param username of type String
     */
    @Override
    public void updateUsername(UserDTO user, String username) {

        User u = em.find(User.class, user.getId());
        u.setUsername(user.getUsername());
        updateUser(u);
    }

    /**
     * Method used to get all users by usertype
     *
     * @param idUsertype
     * @return list of usersDto
     */

    @Override
    public List<UserDTO> getUsersByUsertype(int idUsertype) {
        List<UserDTO> resultlist = new ArrayList<>();
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<User> cq = cb.createQuery(User.class);
        Root<User> user = cq.from(User.class);
        cq.select(user);
        TypedQuery<User> q = em.createQuery(cq);

        List<User> users = q.getResultList();

        for (User u : users) {
            Usertype usertype = u.getUsertype();
            if (usertype.getId() == idUsertype) {
                resultlist.add(conv.fromEntityUser(u));
            }
        }
        return resultlist;
    }

    /**
     * Method used to get Admin
     *
     * @return userDto
     */
    @Override
    public UserDTO getAdmin() {
        int idAdminUsertype = 1;
        List<UserDTO> users = getUsersByUsertype(idAdminUsertype);
        if (!users.isEmpty()) {
            return users.get(0);
        }
        return null;
    }

    /**
     * Method used to get all Inactive Office Managers
     *
     * @return list of usersDto
     */
    @Override
    public List<UserDTO> getInactiveOfficeManagers() {
        List<UserDTO> resultlist = new ArrayList<>();
        UserDTO admin = getAdmin();

        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<User> cq = cb.createQuery(User.class);
        Root<User> user = cq.from(User.class);
        cq.select(user);
        TypedQuery<User> q = em.createQuery(cq);

        List<User> users = q.getResultList();

        for (User u : users) {
            User replacementUser = u.getUser();
            if (u.getIsactive() == 0 && replacementUser != null
                    && replacementUser.getId() == admin.getId()) {
                resultlist.add(conv.fromEntityUser(u));
            }
        }
        return resultlist;
    }

    /**
     * @param email
     * @return UserDTO Returns the user with the specific email address if
     * exists, else returns null
     */
    @Override
    public UserDTO getUserDTOByEmail(String email) {
        UserDTO tempUser = null;
        List<User> users = getAllUsers();
        for (User u : users) {
            if (u.getEmail().equals(email)) {
                tempUser = conv.fromEntityUser(u);
            }
        }
        return tempUser;
    }

    /**
     * Method used to check if user is office manager
     *
     * @param ud
     * @return boolean
     */
    @Override
    public boolean isOfficeManager(UserDTO ud) {
        User u = em.find(User.class, ud.getId());
        if (u == null) {
            return false;
        } else {
            Usertype usertype = u.getUsertype();
            if (usertype.getId() == 2) {
                return true;
            }
        }
        return false;
    }

    /**
     * Method used to check if user is Technichan
     *
     * @param ud
     * @return boolean
     */
    @Override
    public boolean isTechnician(UserDTO ud) {
        User u = em.find(User.class, ud.getId());
        if (u == null) {
            return false;
        } else {
            Usertype usertype = u.getUsertype();
            if (usertype.getId() == 3) {
                return true;
            }
        }
        return false;
    }

    /**
     * Method used to check if user is Admin
     *
     * @param ud
     * @return boolean
     */
    @Override
    public boolean isAdmin(UserDTO ud) {
        User u = em.find(User.class, ud.getId());
        if (u == null) {
            return false;
        } else {
            Usertype usertype = u.getUsertype();
            if (usertype.getId() == 1) {
                return true;
            }
        }
        return false;
    }

    /**
     * Method used to check if user is Basic User
     *
     * @param ud
     * @return boolean
     */
    @Override
    public boolean isBasicUser(UserDTO ud) {
        User u = em.find(User.class, ud.getId());
        if (u == null) {
            return false;
        } else {
            Usertype usertype = u.getUsertype();
            if (usertype.getId() == 4) {
                return true;
            }
        }
        return false;
    }

    /**
     * Method used to get all active office managers
     *
     * @return list of users
     */
    @Override
    public List<UserDTO> getActiveOfficeManagers(UserDTO curUser) {
        List<UserDTO> resultlist = new ArrayList<>();
        if (curUser != null) {
            CriteriaBuilder cb = em.getCriteriaBuilder();
            CriteriaQuery<User> cq = cb.createQuery(User.class);
            Root<User> user = cq.from(User.class);
            cq.select(user);
            TypedQuery<User> q = em.createQuery(cq);

            List<User> users = q.getResultList();

            for (User u : users) {
                UserDTO ud = conv.fromEntityUser(u);
                if (isOfficeManager(ud) && u.getIsactive() == 1 && u.getDomain().getId() == curUser.getIdDomain()) {
                    resultlist.add(conv.fromEntityUser(u));
                }
            }
        }
        return resultlist;
    }

    @Override
    public List<UserDTO> getAllReplacedOfficeManagers() {
        List<UserDTO> resultlist = new ArrayList<>();
        UserDTO admin = getAdmin();

        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<User> cq = cb.createQuery(User.class);
        Root<User> user = cq.from(User.class);
        cq.select(user);
        TypedQuery<User> q = em.createQuery(cq);

        List<User> users = q.getResultList();

        for (User u : users) {
            User replacementUser = u.getUser();
            if (u.getIsactive() == 0 && replacementUser != null
                    && replacementUser.getId() != admin.getId()) {
                resultlist.add(conv.fromEntityUser(u));
            }
        }
        return resultlist;
    }

    @Override
    public List<UserDTO> getUsersOrdered(UserColumns orderColumn, OrderDirection orderDirection) {
        List<UserDTO> userDTOs = new ArrayList<>();

        TypedQuery<User> query = em.createQuery("SELECT u FROM User u ORDER BY u." + orderColumn.getCode() +
                " " + orderDirection.getDir(), User.class);
        List<User> users = query.getResultList();
        for (User u : users) {
            userDTOs.add(conv.fromEntityUser(u));
        }
        return userDTOs;
    }

    /**
     * Method used to get all users ordered by user type and domain ascending
     *
     * @return list of users
     */
    @Override
    public List<UserDTO> getUsersByDomainandUsertype(int idDomain,
                                                     int idUsertype) {
        List<UserDTO> resultlist = new ArrayList<>();
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<User> cq = cb.createQuery(User.class);
        Root<User> user = cq.from(User.class);
        cq.select(user);
        cq.orderBy(cb.asc(user.get(User_.name)));
        TypedQuery<User> q = em.createQuery(cq);
        List<User> users = q.getResultList();

        for (User u : users) {
            Domain d = u.getDomain();
            Usertype type = u.getUsertype();
            if (d.getId() == idDomain && type.getId() == idUsertype) {
                resultlist.add(conv.fromEntityUser(u));
            }
        }
        return resultlist;
    }

    /**
     * Method used to get all users by domain
     *
     * @return list of users
     */
    @Override
    public List<UserDTO> getUsersByDomain(int idDomain) {
        List<UserDTO> resultlist = new ArrayList<>();
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<User> cq = cb.createQuery(User.class);
        Root<User> user = cq.from(User.class);
        cq.select(user);

        TypedQuery<User> q = em.createQuery(cq);
        List<User> users = q.getResultList();

        for (User u : users) {
            Domain d = u.getDomain();
            if (d.getId() == idDomain) {
                resultlist.add(conv.fromEntityUser(u));
            }
        }
        return resultlist;
    }


    /**
     * Method used set domain null
     *
     * @param idDomain
     */
    @Override
    public void setDomainNullByDomainId(int idDomain) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<User> cq = cb.createQuery(User.class);
        Root<User> user = cq.from(User.class);
        cq.select(user);

        TypedQuery<User> q = em.createQuery(cq);
        List<User> users = q.getResultList();

        for (User u : users) {
            Domain d = u.getDomain();
            if (d != null && d.getId() == idDomain) {
                if (em.find(User.class, u.getId()) != null) {
                    u.setDomain(null);
                    em.merge(u);
                }

            }
        }

    }
}
